/*
	File:		Audio_Win32.h
	Author:		dmonroe & nzabel
	Date:		11/10/2012
	Copyright:	Full Sail University

	Purpose:	Win32 audio implementation using FMOD
*/


/*
	TODO:
	PauseAll
	SetMasterVolume
	GetMasterVolume
*/

// Only compile on Win32
#if defined( _WIN32 )

#include "../source/IAudio.h"

#include "../source_Win32/FMod/fmod.h"
#include "../source_Win32/FMod/fmod.hpp"
#pragma comment(lib, "source_Win32/FMod/fmodex_vc.lib")

#include <cassert>

#include "../source/SGD_HandleManager.h"



//******************************************************************
// CAudio
//	- concrete class for handling audio on Win32
//	- creates / handles fmod
//	- loads / plays audio files
class CAudio : public IAudio
{
public:
	// SINGLETON helper:
	friend	IAudio*		IAudio::GetInstance		( void );
	friend	void		IAudio::DeleteInstance	( void );
	
	// SETUP
	virtual	bool		Initialize		( void );
	virtual	bool		Terminate		( void );
	
	// FRAME UPDATE
	virtual bool		Update			( void );


	// SYSTEM TYPES
	//enum EAudioGroup	{ EAG_MUSIC, EAG_SFX, EAG_BOTH	};

	// AUDIO
	virtual HAudio		LoadAudio		( const char* filename );
	virtual bool		UnloadAudio		( HAudio handle );

	virtual bool		Play			( HAudio handle,		bool looping = false );
	virtual bool		IsPlaying		( HAudio handle );
	virtual bool		Pause			( HAudio handle,		bool pause = true );
	
	virtual bool		PauseAll		( EAudioGroup group );

	virtual bool		SetVolume		( HAudio handle,		float volume );
	virtual float		GetVolume		( HAudio handle );
	virtual bool		SetMasterVolume	( EAudioGroup group,	float volume );
	virtual float		GetMasterVolume	( EAudioGroup group );


private:
	// SINGLETON
	static CAudio*		s_Instance;

	CAudio				( void );				// Default constructor
	virtual	~CAudio		( void );				// Destructor

	CAudio				( const CAudio& );		// Copy constructor
	CAudio&	operator=	( const CAudio& );		// Assignment operator

	
	// Wrapper Status
	enum EWrapperStatus
	{	
		E_UNINITIALIZED,
		E_INITIALIZED,
		E_DESTROYED
	};

	EWrapperStatus			m_eStatus;			// wrapper initialization status

	
	// FMod system
	FMOD::System*			m_pSystem;


	// Data storage
	struct SAudioInfo
	{
		char*				szName;
		FMOD::Sound*		pSound;
		FMOD::Channel*		pChannel;
		unsigned int		unRefCount;
	};

	CHandleManager< SAudioInfo >	m_HandleManager;
	
	
	// AUDIO REFERENCE HELPER METHOD
	struct SSearchInfo
	{
		const char*		filename;	// input
		SAudioInfo*		audio;		// output
		HAudio			handle;		// output
	};
	static bool FindAudioByName( CHandle handle, SAudioInfo& data, SSearchInfo* extra );
};
//******************************************************************



//******************************************************************
// SINGLETON

// Instantiate static pointer to null (no instance yet)
/*static*/ CAudio*		CAudio::s_Instance		= NULL;

// Interface singleton accessor
/*static*/ IAudio*		IAudio::GetInstance		( void )
{
	// Allocate singleton on first use
	if( CAudio::s_Instance == NULL )
		CAudio::s_Instance = new CAudio;

	// Return the singleton
	return CAudio::s_Instance;
}

// Interface singleton destructor
/*static*/ void			IAudio::DeleteInstance	( void )
{
	// Deallocate singleton
	delete CAudio::s_Instance;
	CAudio::s_Instance = NULL;
}
//******************************************************************



//******************************************************************
// CONSTRUCTOR
CAudio::CAudio( void )
{
	// Set data members to 'clean' values
	m_eStatus = E_UNINITIALIZED;
	
	m_pSystem = NULL;

	(void)m_HandleManager;		// default initialized already
}
//******************************************************************



//******************************************************************
// DESTRUCTOR
/*virtual*/	CAudio::~CAudio( void )
{
	// Release memory cleanly in Release mode
#if !defined( DEBUG ) && !defined( _DEBUG )
	if( m_eStatus == E_INITIALIZED )
		Terminate();
#endif
}
//******************************************************************



//******************************************************************
// INITIALIZE
/*virtual*/	bool CAudio::Initialize( void )
{
	// Sanity-check the wrapper's status
	assert( m_eStatus == E_UNINITIALIZED && "CAudio::Initialize - wrapper has already been initialized" );


	// Configure FMOD
	FMOD_RESULT rslt = FMOD::System_Create(&m_pSystem);
	assert(rslt == FMOD_OK && "FMod creation failed.");

	m_pSystem->setOutput(FMOD_OUTPUTTYPE_DSOUND);
	m_pSystem->init(100, FMOD_INIT_NORMAL, 0);


	// Success!
	m_eStatus = E_INITIALIZED;
	return true;
}
//******************************************************************



//******************************************************************
// TERMINATE
/*virtual*/	bool CAudio::Terminate( void )
{
	// Sanity-check the wrapper's status
	assert( m_eStatus == E_INITIALIZED && "CAudio::Terminate - wrapper has not been initialized" );

	// Shutdown FMOD
	m_pSystem->release();
	m_pSystem = NULL;

	// Wrapper is now destroyed
	m_eStatus = E_DESTROYED;
	return true;
}
//******************************************************************



//******************************************************************
// UPDATE
//	- update music streams
/*virtual*/ bool CAudio::Update( void )
{
	// Sanity-check the wrapper's status
	assert( m_eStatus == E_INITIALIZED && "CAudio::Update - wrapper has not been initialized" );


	// Refresh FMOD
	m_pSystem->update();
	

	// Continue to run the application
	return true;
}
//******************************************************************



//******************************************************************
// LOAD AUDIO
/*virtual*/ HAudio CAudio::LoadAudio( const char* filename )
{
	// Sanity-check the wrapper's status
	assert( m_eStatus == E_INITIALIZED && "CAudio::LoadAudio - wrapper has not been initialized" );

	
	// Attempt to find the audio in the Handle Manager
	SSearchInfo search = { filename, NULL, HAudio() };
	m_HandleManager.ForEach( &CAudio::FindAudioByName, &search );

	// If it was found, increase the reference & return the existing handle
	if( search.audio != NULL )
	{
		search.audio->unRefCount++;
		return search.handle;
	}

	
	// Otherwise the audio does not already exist.
	// Attempt to determine the file type
	enum EFileFormat	{	E_WAV, E_OGG, E_UNKNOWN	};		// TODO: What file types???
	EFileFormat eFormat = E_UNKNOWN;

	int len = strlen( filename );
	const char* fileExtension = filename + len - 4;

	if( strcmp( fileExtension, ".wav" ) == 0 )
		eFormat = E_WAV;
	else if( strcmp( fileExtension, ".ogg" ) == 0 )
		eFormat = E_OGG;
	else
	{
		// MESSAGE
		assert( false && "CAudio::LoadAudio - invalid file type (only .wav and .ogg supported)" );
		return HAudio();
	}


	// Store info into a temporary variable
	SAudioInfo info = { };

	FMOD_RESULT rslt = m_pSystem->createSound(filename, FMOD_CREATESAMPLE, NULL, &(info.pSound));

	// Did it fail?
	if( rslt != FMOD_OK )
	{
		// MESSAGE
		assert(false && filename && " Failed to Load");
		return HAudio();
	}


	// Set remaining data members
	info.szName = _strdup( filename );
	info.unRefCount	= 1;
	

	// Store the audio in the Handle Manager
	return m_HandleManager.StoreData( info );
}
//******************************************************************



//******************************************************************
// UNLOAD AUDIO
/*virtual*/ bool CAudio::UnloadAudio( HAudio handle )
{
	// Sanity-check the wrapper's status
	assert( m_eStatus == E_INITIALIZED && "CAudio::UnloadAudio - wrapper has not been initialized" );
	assert( handle != INVALID_HANDLE && "CAudio::UnloadAudio - invalid handle" );


	// Get the audio info from the Handle Manager
	SAudioInfo& info = m_HandleManager.GetData( handle );
	
	// Release a reference
	info.unRefCount--;

	
	// Are there other references?
	if( info.unRefCount > 0 )
		return false;


	// Remove the audio info from the Handle Manager
	SAudioInfo old = m_HandleManager.RemoveData( handle );

	// Remove the audio from FMOD
	old.pSound->release();

	// Deallocate the audio's name
	delete[] old.szName;

	return true;
}
//******************************************************************



//******************************************************************
// PLAY
/*virtual*/ bool CAudio::Play( HAudio handle, bool looping )
{
	// Sanity-check the wrapper's status
	assert( m_eStatus == E_INITIALIZED && "CAudio::Play - wrapper has not been initialized" );
	assert( handle != INVALID_HANDLE && "CAudio::Play - invalid handle" );


	// Get the audio info from the Handle Manager
	SAudioInfo& info = m_HandleManager.GetData( handle );

	// Play the audio
	FMOD_RESULT rslt = m_pSystem->playSound(FMOD_CHANNEL_FREE, info.pSound, false, &(info.pChannel));
	if(rslt != FMOD_OK)
	{
		assert(false && "CAudio::Play - Failed to Play");
		return false;
	}

	return true;
}
//******************************************************************



//******************************************************************
// IS PLAYING
/*virtual*/ bool CAudio::IsPlaying( HAudio handle )
{
	// Sanity-check the wrapper's status
	assert( m_eStatus == E_INITIALIZED && "CAudio::IsPlaying - wrapper has not been initialized" );
	assert( handle != INVALID_HANDLE && "CAudio::IsPlaying - invalid handle" );


	// Get the audio info from the Handle Manager
	SAudioInfo& info = m_HandleManager.GetData( handle );

	// Check the status
	bool playing = false;
	info.pChannel->isPlaying(&playing);


	return playing;
}
//******************************************************************



//******************************************************************
// PAUSE
/*virtual*/ bool CAudio::Pause( HAudio handle, bool pause )
{
	// Sanity-check the wrapper's status
	assert( m_eStatus == E_INITIALIZED && "CAudio::Pause - wrapper has not been initialized" );
	assert( handle != INVALID_HANDLE && "CAudio::Pause - invalid handle" );


	// Get the audio info from the Handle Manager
	SAudioInfo& info = m_HandleManager.GetData( handle );

	// Stop the audio
	info.pChannel->stop();
	

	return true;
}
//******************************************************************



//******************************************************************
// PAUSE ALL
/*virtual*/ bool CAudio::PauseAll( EAudioGroup group )
{
	// Sanity-check the wrapper's status
	assert( m_eStatus == E_INITIALIZED && "CAudio::PauseAll - wrapper has not been initialized" );
	

	// TODO:
	return false;	// not implemented
}
//******************************************************************



//******************************************************************
// SET VOLUME
/*virtual*/ bool CAudio::SetVolume( HAudio handle, float volume )
{
	// Sanity-check the wrapper's status
	assert( m_eStatus == E_INITIALIZED && "CAudio::SetVolume - wrapper has not been initialized" );
	assert( handle != INVALID_HANDLE && "CAudio::SetVolume - invalid handle" );


	// Get the audio info from the Handle Manager
	SAudioInfo& info = m_HandleManager.GetData( handle );

	// Set the volume
	info.pChannel->setVolume(volume);


	return true;
}
//******************************************************************



//******************************************************************
// GET VOLUME
/*virtual*/ float CAudio::GetVolume( HAudio handle )
{
	// Sanity-check the wrapper's status
	assert( m_eStatus == E_INITIALIZED && "CAudio::GetVolume - wrapper has not been initialized" );
	assert( handle != INVALID_HANDLE && "CAudio::GetVolume - invalid handle" );


	// Get the audio info from the Handle Manager
	SAudioInfo& info = m_HandleManager.GetData( handle );

	// Get the volume
	float volume = 0.0f;
	info.pChannel->getVolume(&volume);


	return volume;
}
//******************************************************************



//******************************************************************
// SET MASTER VOLUME
/*virtual*/ bool CAudio::SetMasterVolume( EAudioGroup group, float volume )
{
	// Sanity-check the wrapper's status
	assert( m_eStatus == E_INITIALIZED && "CAudio::SetMasterVolume - wrapper has not been initialized" );


	// TODO:
	return false;	// not implemented
}
//******************************************************************



//******************************************************************
// GET MASTER VOLUME
/*virtual*/ float CAudio::GetMasterVolume( EAudioGroup group )
{
	// Sanity-check the wrapper's status
	assert( m_eStatus == E_INITIALIZED && "CAudio::GetMasterVolume - wrapper has not been initialized" );

	// TODO:
	return 0.0f;	// not implemented
}
//******************************************************************



//******************************************************************
// FIND AUDIO BY NAME
//	- callback function for the Handle Manager
//	- returns false to stop searching
bool CAudio::FindAudioByName( CHandle handle, SAudioInfo& data, SSearchInfo* extra )
{
	// Compare the names
	if( strcmp( data.szName, extra->filename ) == 0 )
	{
		// Texture does exist!
		extra->audio  = &data;
		extra->handle = handle;
		return false;
	}

	// Did not find yet
	return true;
}
//******************************************************************


#endif	// defined( _WIN32 )
